package fr.vahren.dotastrat.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

import fr.vahren.dotastrat.game.D2SGame;
import fr.vahren.dotastrat.game.D2SGameState;
import fr.vahren.dotastrat.game.D2SMessageType;
import fr.vahren.dotastrat.model.builder.D2SStatsGen;

public class D2SMap {

	public static List<D2SZone> zones = new ArrayList<>();

	private D2SAncient radiantAncient = new D2SAncient("ancient",
			D2SUnitClass.ANCIENT, D2SStatsGen.ancientStats());
	private D2SAncient direAncient = new D2SAncient("ancient",
			D2SUnitClass.ANCIENT, D2SStatsGen.ancientStats());

	private D2SGame game;

	public final static int NB_ZONES = 55;
	public final static int RADIANT_SPAWN = 32;// 49
	public final static int DIRE_SPAWN = 32;// 8
	public final static int TOP_RUNE = 25;
	public final static int BOT_RUNE = 43;

	public static Map<Integer, List<Integer>> adjacencyList;
	public static Map<Integer, List<Integer>> adjacencyListWithWalls;

	static {
		adjacencyList = new HashMap<>();
		adjacencyList.put(0, new ArrayList<Integer>());
		adjacencyList.put(1, Arrays.asList(2, 9, 15));
		adjacencyList.put(2, Arrays.asList(1, 9, 10, 3));
		adjacencyList.put(3, Arrays.asList(2, 4, 10));
		adjacencyList.put(4, Arrays.asList(3, 5, 11));
		adjacencyList.put(5, Arrays.asList(4, 6, 11, 12));
		adjacencyList.put(6, Arrays.asList(5, 7, 13));
		adjacencyList.put(7, Arrays.asList(6, 8, 13, 14));
		adjacencyList.put(8, Arrays.asList(7));
		adjacencyList.put(9, Arrays.asList(1, 2, 10, 16, 57));
		adjacencyList.put(10, Arrays.asList(2, 3, 9, 11, 57));
		adjacencyList.put(11, Arrays.asList(4, 10, 12, 17, 18, 19));
		adjacencyList.put(12, Arrays.asList(5, 11, 13, 19, 20));
		adjacencyList.put(13, Arrays.asList(6, 7, 12, 14, 20));
		adjacencyList.put(14, Arrays.asList(7, 21, 20));
		adjacencyList.put(15, Arrays.asList(1, 16, 22, 23));
		adjacencyList.put(16, Arrays.asList(15, 9, 57, 25, 23));
		adjacencyList.put(17, Arrays.asList(57, 11, 18, 25));
		adjacencyList.put(18, Arrays.asList(17, 11, 19, 26));
		adjacencyList.put(19, Arrays.asList(18, 11, 12, 26, 27));
		adjacencyList.put(20, Arrays.asList(12, 13, 14, 21, 27));
		adjacencyList.put(21, Arrays.asList(14, 20, 27, 28));
		adjacencyList.put(22, Arrays.asList(15, 23, 29));
		adjacencyList.put(23, Arrays.asList(15, 16, 22, 24, 30));
		adjacencyList.put(24, Arrays.asList(23, 25, 31, 30));
		adjacencyList.put(25, Arrays.asList(16, 17, 26, 32, 24));
		adjacencyList.put(26, Arrays.asList(25, 18, 19, 34, 33, 32, 43));
		adjacencyList.put(27, Arrays.asList(19, 20, 21, 28, 35, 34));
		adjacencyList.put(28, Arrays.asList(21, 27, 36));
		adjacencyList.put(29, Arrays.asList(22, 30, 37));
		adjacencyList.put(30, Arrays.asList(29, 23, 24, 39, 38, 37));
		adjacencyList.put(31, Arrays.asList(24, 25, 32, 40, 39));
		adjacencyList.put(32, Arrays.asList(25, 26, 33, 40, 31));
		adjacencyList.put(33, Arrays.asList(26, 32, 41, 43));
		adjacencyList.put(34, Arrays.asList(26, 27, 35, 43));
		adjacencyList.put(35, Arrays.asList(34, 27, 36, 43, 56));
		adjacencyList.put(36, Arrays.asList(28, 35, 44, 56));
		adjacencyList.put(37, Arrays.asList(29, 30, 38, 45, 46));
		adjacencyList.put(38, Arrays.asList(30, 37, 46, 52, 40, 39));
		adjacencyList.put(39, Arrays.asList(30, 31, 40, 28));
		adjacencyList.put(40, Arrays.asList(31, 32, 41, 47, 52, 38, 39));
		adjacencyList.put(41, Arrays.asList(33, 42, 47, 40));
		adjacencyList.put(42, Arrays.asList(43, 44, 48, 41, 56));
		adjacencyList.put(43, Arrays.asList(26, 34, 35, 56, 42, 33));
		adjacencyList.put(44, Arrays.asList(36, 42, 48, 55));
		adjacencyList.put(45, Arrays.asList(37, 46, 50));
		adjacencyList.put(46, Arrays.asList(45, 38, 37, 50, 51));
		adjacencyList.put(47, Arrays.asList(40, 41, 48, 53, 54));
		adjacencyList.put(48, Arrays.asList(42, 44, 55, 54, 47));
		adjacencyList.put(49, Arrays.asList(50));
		adjacencyList.put(50, Arrays.asList(45, 46, 51));
		adjacencyList.put(51, Arrays.asList(50, 46, 52));
		adjacencyList.put(52, Arrays.asList(38, 40, 51, 53));
		adjacencyList.put(53, Arrays.asList(52, 47, 54));
		adjacencyList.put(54, Arrays.asList(53, 47, 48, 55));
		adjacencyList.put(55, Arrays.asList(48, 54, 44));
		adjacencyList.put(56, Arrays.asList(43, 35, 36, 42));
		adjacencyList.put(57, Arrays.asList(16, 17, 10, 9));

		adjacencyListWithWalls = new HashMap<>();
		adjacencyListWithWalls.put(0, new ArrayList<Integer>());
		adjacencyListWithWalls.put(1, Arrays.asList(2, 9, 15));
		adjacencyListWithWalls.put(2, Arrays.asList(1, 3));
		adjacencyListWithWalls.put(3, Arrays.asList(2, 4, 10));
		adjacencyListWithWalls.put(4, Arrays.asList(3, 5, 11));
		adjacencyListWithWalls.put(5, Arrays.asList(4, 6, 11, 12));
		adjacencyListWithWalls.put(6, Arrays.asList(5, 7, 13));
		adjacencyListWithWalls.put(7, Arrays.asList(6, 8, 13, 14));
		adjacencyListWithWalls.put(8, Arrays.asList(7));
		adjacencyListWithWalls.put(9, Arrays.asList(1, 10, 57));
		adjacencyListWithWalls.put(10, Arrays.asList(3, 9, 11));
		adjacencyListWithWalls.put(11, Arrays.asList(4, 10, 12, 17, 18, 19));
		adjacencyListWithWalls.put(12, Arrays.asList(5, 11, 13, 19, 20));
		adjacencyListWithWalls.put(13, Arrays.asList(6, 7, 12, 14, 20));
		adjacencyListWithWalls.put(14, Arrays.asList(7, 21, 20));
		adjacencyListWithWalls.put(15, Arrays.asList(1, 16, 22));
		adjacencyListWithWalls.put(16, Arrays.asList(15, 57, 25));
		adjacencyListWithWalls.put(17, Arrays.asList(57, 11, 18));
		adjacencyListWithWalls.put(18, Arrays.asList(17, 11, 19, 26));
		adjacencyListWithWalls.put(19, Arrays.asList(18, 12, 26));
		adjacencyListWithWalls.put(20, Arrays.asList(12, 13, 21, 27));
		adjacencyListWithWalls.put(21, Arrays.asList(14, 20, 28));
		adjacencyListWithWalls.put(22, Arrays.asList(15, 23, 29));
		adjacencyListWithWalls.put(23, Arrays.asList(22, 24));
		adjacencyListWithWalls.put(24, Arrays.asList(23, 25, 30));
		adjacencyListWithWalls.put(25, Arrays.asList(16, 26, 32, 24));
		adjacencyListWithWalls.put(26, Arrays.asList(25, 18, 19, 32, 43));
		adjacencyListWithWalls.put(27, Arrays.asList(20, 28, 35));
		adjacencyListWithWalls.put(28, Arrays.asList(21, 27, 36));
		adjacencyListWithWalls.put(29, Arrays.asList(22, 30, 37));
		adjacencyListWithWalls.put(30, Arrays.asList(29, 23, 24, 39));
		adjacencyListWithWalls.put(31, Arrays.asList(25, 32, 40, 39));
		adjacencyListWithWalls.put(32, Arrays.asList(25, 26, 33, 31));
		adjacencyListWithWalls.put(33, Arrays.asList(32, 41, 43));
		adjacencyListWithWalls.put(34, Arrays.asList(43));
		adjacencyListWithWalls.put(35, Arrays.asList(27, 36, 43));
		adjacencyListWithWalls.put(36, Arrays.asList(28, 35, 44, 56));
		adjacencyListWithWalls.put(37, Arrays.asList(29, 38, 45));
		adjacencyListWithWalls.put(38, Arrays.asList(37, 46, 52, 40, 39));
		adjacencyListWithWalls.put(39, Arrays.asList(30, 31, 40, 28));
		adjacencyListWithWalls.put(40, Arrays.asList(31, 41, 38, 39));
		adjacencyListWithWalls.put(41, Arrays.asList(33, 42, 47, 40));
		adjacencyListWithWalls.put(42, Arrays.asList(43, 44, 48, 41));
		adjacencyListWithWalls.put(43, Arrays.asList(26, 34, 35, 56, 42, 33));
		adjacencyListWithWalls.put(44, Arrays.asList(36, 42, 48, 55));
		adjacencyListWithWalls.put(45, Arrays.asList(37, 46, 50));
		adjacencyListWithWalls.put(46, Arrays.asList(45, 38, 50, 51));
		adjacencyListWithWalls.put(47, Arrays.asList(41, 48, 53, 54));
		adjacencyListWithWalls.put(48, Arrays.asList(42, 44, 55, 47));
		adjacencyListWithWalls.put(49, Arrays.asList(50));
		adjacencyListWithWalls.put(50, Arrays.asList(45, 46, 51));
		adjacencyListWithWalls.put(51, Arrays.asList(50, 46, 52));
		adjacencyListWithWalls.put(52, Arrays.asList(38, 51, 53));
		adjacencyListWithWalls.put(53, Arrays.asList(52, 47, 54));
		adjacencyListWithWalls.put(54, Arrays.asList(53, 47, 55));
		adjacencyListWithWalls.put(55, Arrays.asList(48, 54, 44));
		adjacencyListWithWalls.put(56, Arrays.asList(43, 36));
		adjacencyListWithWalls.put(57, Arrays.asList(16, 17, 9));

		zones.add(new D2SZone(1, null, null, null, null, null,
				D2SShop.SIDE_SHOP));
		zones.add(new D2SZone(2, null, null, Arrays.asList(new D2STower(
				"tower", 0)), null, null, null));
		zones.add(new D2SZone(3, null, null, null, null, null, null));
		zones.add(new D2SZone(4, null, null, Arrays.asList(new D2STower(
				"tower", 1)), null, null, null));
		zones.add(new D2SZone(5, null, null, null, null, null, null));
		zones.add(new D2SZone(6, null, null, Arrays.asList(new D2STower(
				"tower", 2)), new D2SBarracks("barracks",
				D2SUnitClass.BARRACKS, D2SStatsGen.barracksStats()),
				new D2SBarracks("barracks", D2SUnitClass.BARRACKS, D2SStatsGen
						.barracksStats()), null));
		zones.add(new D2SZone(7, null, null, Arrays.asList(new D2STower(
				"tower", 3), new D2STower("tower", 3)), null, null, null));
		zones.add(new D2SZone(8, null, null, null, null, null, D2SShop.FOUNTAIN));
		zones.add(new D2SZone(9, D2SCreepType.MEDIUM, null, null, null, null,
				null));
		zones.add(new D2SZone(10, D2SCreepType.SMALL, null, null, null, null,
				null));
		zones.add(new D2SZone(11, D2SCreepType.SMALL, D2SCreepType.MEDIUM,
				null, null, null, null));
		zones.add(new D2SZone(12, null, null, Arrays.asList(new D2STower(
				"tower", 1)), null, null, null));
		zones.add(new D2SZone(13, null, null, Arrays.asList(new D2STower(
				"tower", 2)), new D2SBarracks("barracks",
				D2SUnitClass.BARRACKS, D2SStatsGen.barracksStats()),
				new D2SBarracks("barracks", D2SUnitClass.BARRACKS, D2SStatsGen
						.barracksStats()), null));
		zones.add(new D2SZone(14, null, null, Arrays.asList(new D2STower(
				"tower", 2)), new D2SBarracks("barracks",
				D2SUnitClass.BARRACKS, D2SStatsGen.barracksStats()),
				new D2SBarracks("barracks", D2SUnitClass.BARRACKS, D2SStatsGen
						.barracksStats()), null));
		zones.add(new D2SZone(15, null, null, Arrays.asList(new D2STower(
				"tower", 0)), null, null, null));
		zones.add(new D2SZone(16, null, null, null, null, null, null));
		zones.add(new D2SZone(17, D2SCreepType.BIG, null, null, null, null,
				null));
		zones.add(new D2SZone(18, null, null, null, null, null, null));
		zones.add(new D2SZone(19, null, null, null, null, null, null));
		zones.add(new D2SZone(20, null, null, null, null, null, null));
		zones.add(new D2SZone(21, null, null, null, null, null, null));
		zones.add(new D2SZone(22, null, null, null, null, null, null));
		zones.add(new D2SZone(23, null, null, null, null, null,
				D2SShop.SECRET_SHOP));
		zones.add(new D2SZone(24, D2SCreepType.ANCIENT, null, null, null, null,
				null));
		zones.add(new D2SZone(25, null, null, null, null, null, null));
		zones.add(new D2SZone(26, null, null, Arrays.asList(new D2STower(
				"tower", 0)), null, null, null));
		zones.add(new D2SZone(27, null, null, null, null, null,
				D2SShop.SECRET_SHOP));
		zones.add(new D2SZone(28, null, null, Arrays.asList(new D2STower(
				"tower", 1)), null, null, null));
		zones.add(new D2SZone(29, null, null, Arrays.asList(new D2STower(
				"tower", 1)), null, null, null));
		zones.add(new D2SZone(30, null, null, null, null, null, null));
		zones.add(new D2SZone(31, null, null, Arrays.asList(new D2STower(
				"tower", 0)), null, null, null));
		zones.add(new D2SZone(32, null, null, null, null, null, null));
		zones.add(new D2SZone(33, null, null, null, null, null, null));
		zones.add(new D2SZone(34, D2SCreepType.ROSHAN, null, null, null, null,
				null));
		zones.add(new D2SZone(35, D2SCreepType.ANCIENT, null, null, null, null,
				null));
		zones.add(new D2SZone(36, null, null, Arrays.asList(new D2STower(
				"tower", 0)), null, null, null));
		zones.add(new D2SZone(37, null, null, null, null, null, null));
		zones.add(new D2SZone(38, null, null, Arrays.asList(new D2STower(
				"tower", 1)), null, null, null));
		zones.add(new D2SZone(39, null, null, null, null, null, null));
		zones.add(new D2SZone(40, D2SCreepType.SMALL, D2SCreepType.MEDIUM,
				null, null, null, null));
		zones.add(new D2SZone(41, D2SCreepType.MEDIUM, null, null, null, null,
				null));
		zones.add(new D2SZone(42, D2SCreepType.BIG, null, null, null, null,
				null));
		zones.add(new D2SZone(43, null, null, null, null, null, null));
		zones.add(new D2SZone(44, null, null, null, null, null,
				D2SShop.SIDE_SHOP));
		zones.add(new D2SZone(45, null, null, Arrays.asList(new D2STower(
				"tower", 2)), new D2SBarracks("barracks",
				D2SUnitClass.BARRACKS, D2SStatsGen.barracksStats()),
				new D2SBarracks("barracks", D2SUnitClass.BARRACKS, D2SStatsGen
						.barracksStats()), null));
		zones.add(new D2SZone(46, null, null, Arrays.asList(new D2STower(
				"tower", 2)), new D2SBarracks("barracks",
				D2SUnitClass.BARRACKS, D2SStatsGen.barracksStats()),
				new D2SBarracks("barracks", D2SUnitClass.BARRACKS, D2SStatsGen
						.barracksStats()), null));
		zones.add(new D2SZone(47, null, null, null, null, null, null));
		zones.add(new D2SZone(48, D2SCreepType.SMALL, null, null, null, null,
				null));
		zones.add(new D2SZone(49, null, null, null, null, null,
				D2SShop.FOUNTAIN));
		zones.add(new D2SZone(50, null, null, Arrays.asList(new D2STower(
				"tower", 3), new D2STower("tower", 3)), null, null, null));
		zones.add(new D2SZone(51, null, null, Arrays.asList(new D2STower(
				"tower", 2)), new D2SBarracks("barracks",
				D2SUnitClass.BARRACKS, D2SStatsGen.barracksStats()),
				new D2SBarracks("barracks", D2SUnitClass.BARRACKS, D2SStatsGen
						.barracksStats()), null));
		zones.add(new D2SZone(52, null, null, null, null, null, null));
		zones.add(new D2SZone(53, null, null, Arrays.asList(new D2STower(
				"tower", 1)), null, null, null));
		zones.add(new D2SZone(54, null, null, null, null, null, null));
		zones.add(new D2SZone(55, null, null, Arrays.asList(new D2STower(
				"tower", 0)), null, null, null));
		zones.add(new D2SZone(56, null, null, null, null, null, null));
		zones.add(new D2SZone(57, null, null, null, null, null, null));

	}

	public D2SMap(D2SGame g) {
		game = g;
	}

	public boolean areAdjacent(int zoneId1, int zoneId2) {

		for (int z : adjacencyList.get(zoneId1)) {
			if (z == zoneId2) {
				return true;
			}
		}
		return false;
	}

	public boolean canMoveTo(int zoneId1, int zoneId2) {

		for (int z : adjacencyListWithWalls.get(zoneId1)) {
			if (z == zoneId2) {
				return true;
			}
		}

		return false;
	}

	/**
	 * @return the zones
	 */
	public List<D2SZone> getZones() {
		return zones;
	}

	public void putHeroesAtSpawn(List<D2SHeroUnit> controlledUnits, D2SSide side) {
		D2SZone spawn = getZone(side == D2SSide.DIRE ? DIRE_SPAWN
				: RADIANT_SPAWN);
		for (D2SHeroUnit unit : controlledUnits) {
			game.sendMessageTo(game.getPlayerFromUnit(unit.getId()).getId(),
					unit.getName() + " spawning", D2SMessageType.RESPAWN);
			spawn.putUnit(unit);
			unit.heal();
		}
	}

	public D2SZone getZone(int zoneId) {
		return zones.get(zoneId - 1);
	}

	@SuppressWarnings("unchecked")
	public JSONObject generateInfo(D2SGameState phase) {
		JSONObject map = new JSONObject();

		map.put("radiantAncient", radiantAncient.generateInfo(phase));
		map.put("direAncient", direAncient.generateInfo(phase));
		JSONArray zones = new JSONArray();

		for (D2SZone zone : D2SMap.zones) {
			zones.add(zone.generateInfo(phase));
		}
		map.put("zones", zones);

		return map;
	}

	/**
	 * @return the radiantAncient
	 */
	public D2SAncient getRadiantAncient() {
		return radiantAncient;
	}

	/**
	 * @param radiantAncient
	 *            the radiantAncient to set
	 */
	public void setRadiantAncient(D2SAncient radiantAncient) {
		this.radiantAncient = radiantAncient;
	}

	/**
	 * @return the direAncient
	 */
	public D2SAncient getDireAncient() {
		return direAncient;
	}

	/**
	 * @param direAncient
	 *            the direAncient to set
	 */
	public void setDireAncient(D2SAncient direAncient) {
		this.direAncient = direAncient;
	}

	public int getZoneFromHero(int id) {
		for (D2SZone zone : zones) {
			for (D2SHeroUnit unit : zone.getUnits()) {
				if (unit.getId() == id) {
					return zone.getId();
				}
			}
		}
		return 0;
	}

	public void move(D2SHeroUnit unit, int startZone, int id) {
		getZone(id).putUnit(getZone(startZone).removeUnit(unit));

	}

	public boolean isRangeOk(D2SHeroUnit unit, D2SUnit target, int range) {
		if (range == 1) {
			return getZoneFromHero(unit.getId()) == getZoneFromUnit(target);
		} else if (range == 2) {
			return areAdjacent(getZoneFromHero(unit.getId()),
					getZoneFromUnit(target));
		} else {
			// global range
			return true;
		}
	}

	private int getZoneFromUnit(D2SUnit target) {
		if (target instanceof D2SHeroUnit) {
			return getZoneFromHero(target.getId());
		} else {
			// search for creeps TODO
			return 0;
		}
	}

	public void removeUnit(D2SHeroUnit unit) {
		getZone(getZoneFromHero(unit.getId())).removeUnit(unit);
	}

	public static void setZones(List<D2SZone> zones2) {
		zones = zones2;
	}

	public boolean isRangeOk(D2SHeroUnit unit, D2SZone targetZone, int range) {
		if (range == 1) {
			return getZoneFromHero(unit.getId()) == targetZone.getId();
		} else if (range == 2) {
			return areAdjacent(getZoneFromHero(unit.getId()),
					targetZone.getId());
		} else {
			return true;
		}
	}

	public void removeCreep(D2SCreeps c) {
		for (D2SZone zone : zones) {
			for (D2SCreeps creep : zone.getCreeps()) {
				if (creep.getId() == c.getId()) {
					zone.removeCreep(creep);
					return;
				}
			}
		}
	}

	public void move(D2SCreeps creep, int src, Integer dst) {
		getZone(src).removeCreep(creep);
		getZone(dst).putCreep(creep);
	}

}
